import { AbstractMesh, ActionManager, ArcRotateCamera, Axis, Camera, Color3, Color4, DeviceSourceManager, DeviceType, Engine, HemisphericLight, KeyboardEventTypes, Mesh, MeshBuilder, Nullable, PointLight, PointerDragBehavior, PointerEventTypes, PointerInput, Scalar, Scene, SceneLoader, Sound, StandardMaterial, Vector3 } from "babylonjs";
import 'babylonjs-loaders';
import GBGUI from "../gui";

export default class GearBump {
  private engine: Engine;
  private scene: Scene;
  private divs: Map<string, HTMLDivElement>;
  private originPos: Map<string, Vector3>;
  private explosionPos: Map<string, Vector3>;
  private soundClosure: Sound;
  private pickedMesh: Nullable<AbstractMesh> = null;

  constructor(private readonly canvas: HTMLCanvasElement) {
    this.engine = new Engine(this.canvas);
    this.scene = this.createScene();

    this.engine.runRenderLoop(() => {
      this.scene.render();
    })

    window.addEventListener('resize', () => {
      this.engine.resize();
    })

    this.divs = new Map<string, HTMLDivElement>();
    this.originPos = new Map<string, Vector3>();
    this.explosionPos = new Map<string, Vector3>();
  }

  private createScene(): Scene {
    const scene = new Scene(this.engine);

    const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.5, 6, new Vector3(0, 0, 0));
    camera.attachControl(true)

    const light = new HemisphericLight('light', new Vector3(1, 1, 0));
    light.intensity = 0.7;
    const pl = new PointLight('pl', Vector3.Zero());
    pl.intensity = 0.3;
    pl.position = camera.position;

    const box = MeshBuilder.CreateBox('box');

    const mat = new StandardMaterial('mat');
    mat.diffuseColor = new Color3(0.8, 0.8, 0.9);

    SceneLoader.ImportMeshAsync('', '/', 'CB-BM10.glb').then((value) => {
      box.isVisible = false;
      value.meshes.forEach(mesh => {
        if (mesh.name != '__root__') {
          // root 为 父项便于整体旋转
          mesh.parent = value.meshes[0];
          // 每个 mesh 增加拖动行为
          const pdb = new PointerDragBehavior();
          pdb.onDragObservable.add(data => {
            if (mesh.material) {
              const importMeshMat = mesh.material as StandardMaterial
              if (Vector3.Distance(mesh.position, Vector3.Zero()) < 0.05) {
                importMeshMat.diffuseColor = new Color3(0, 0.6, 0.3);
              } else {
                importMeshMat.diffuseColor = new Color3(0, 0.3, 0.8);
              }
            }
          })
          mesh.addBehavior(pdb);
        } 
        mesh.material = mat.clone('mat_' + mesh.name);
        this.divs.set(mesh.name, this.createDiv(mesh.name));
        this.originPos.set(mesh.name, mesh.position.clone());
      });

      // (value.meshes[1].behaviors[0] as PointerDragBehavior).options.dragPlaneNormal = Axis.Y;

      this.showInfo(scene);      
      
      this.setExplosionPosition();

    })
    // 加载声音
    this.soundClosure = new Sound('closure', '/closure.mp3');

    // gui
    const gui = new GBGUI();
    // gui.panel.background = 'green';
    gui.panel.width = '200px';
    gui.setPanelPosition();
    let animExposion = false;
    let animAmount = 0;
    let animDir = 1;
    scene.onBeforeRenderObservable.add(() => {
      if (animExposion) {
        animAmount += animDir * 0.01;
        if (animAmount >= 1 || animAmount <= 0) animExposion = false;
        this.explosionPos.forEach((v, key) => {
          scene.getMeshByName(key)!.position = Vector3.Lerp(this.originPos.get(key)!, v, animAmount);
        })
      }
      
    })
    gui.addCheckbox('爆炸图', (value) => {
      animExposion = true;
      if (value) {
        // 爆炸图
        animDir = 1;
        animAmount = 0;
        // this.explosionPos.forEach((v, key) => {
        //   scene.getMeshByName(key)!.position = v;
        // })
      } else {
        // 原始位置
        animDir = -1;
        animAmount = 1;
        // this.originPos.forEach((v, key) => {
        //   scene.getMeshByName(key)!.position = v;
        // })
      }
    })

    // 输入
    // this.getMouseControl();
    this.changeDragMeshColor(scene, camera);

    

    return scene;
  }

  private setExplosionPosition() {
    this.originPos.forEach((value, key) => this.explosionPos.set(key, value.clone()));
    // this.explosionPos = this.originPos;
    this.explosionPos.forEach((v, key) => {
      if (key == '泵盖外') v.y -= 1.9;
      if (key.includes('滑动轴承外盖')) v.y -= 1.7;
      if (key == '主动轮') v.y -= 1.6;
      if (key == '从动轮') v.y -= 1.6;
      if (key == '长轴') v.y -= 1.1;
      if (key == '长轴齿轮键') { v.y -= 1.1; v.z += 0.1; }
      if (key == '短轴') v.y -= 1.1;
      if (key == '短轴齿轮键') { v.y -= 1.1; v.z += 0.1; }
      // if (key == '泵体') v.x += 1.7;
      // if (key == '泵盖内') v.y -= 1;
      if (key == '泵盖内') v.y += 0.3;
      if (key.includes('滑动轴承内盖')) v.y += 0.1;
      if (key == '轴承座') v.y += 0.8;
      if (key == '密封圈') v.y += 1.0;
      if (key.includes('螺栓')) v.y += 1.3;
    })
  }

  private createDiv(name: string): HTMLDivElement {
    const div = document.createElement('div');
    document.getElementsByTagName('body')[0].appendChild(div);
    div.id = name;

    return div;
  }

  private showInfo(scene: Scene) {
    let curDiv: HTMLDivElement;
    let lastDiv: HTMLDivElement;
    let onExit = true;

    scene.onBeforeRenderObservable.add(() => {
      const pickResult = scene.pick(scene.pointerX, scene.pointerY, (mesh) => {
        return mesh.isVisible && mesh.isReady() && !mesh.name.includes('Line');
      })
      if (pickResult.hit) {
        const name = pickResult.pickedMesh?.name;
        if (!name) return;
        curDiv = this.divs.get(name)!;
        if (lastDiv && curDiv != lastDiv) {
          lastDiv.style.display = 'none';
          lastDiv = curDiv;
        }
        curDiv.style.position = 'fixed';
        curDiv.style.width = '150px';
        curDiv.style.height = '20px';
        curDiv.style.left = scene.pointerX + 'px';
        curDiv.style.top = scene.pointerY - 30 + 'px';
        curDiv.style.display = 'block';
        curDiv.style.color = 'white';
        curDiv.style.cursor = 'pointer';
        curDiv.innerHTML = name;
        onExit = true;
        lastDiv = curDiv;
      } else if (curDiv && onExit) {
        curDiv.style.display = 'none';
        onExit = false;
      }
    })
  }

  private changeDragMeshColor(scene: Scene, camera: Camera) {
    const LineX = MeshBuilder.CreateDashedLines('LineX', {
      points: [new Vector3(-camera.maxZ, 0, 0), new Vector3(camera.maxZ, 0, 0)],
      dashSize: 2,
      gapSize: 1,
      dashNb: Math.floor(camera.maxZ * 5)
    });
    LineX.color = Color3.Red();
    LineX.isVisible = false;

    const LineY = MeshBuilder.CreateDashedLines('LineY', {
      points: [new Vector3(0, -camera.maxZ, 0), new Vector3(0, camera.maxZ, 0)],
      dashSize: 2,
      gapSize: 1,
      dashNb: Math.floor(camera.maxZ * 5)
    });
    LineY.color = Color3.Green();
    LineY.isVisible = false;

    const LineZ = MeshBuilder.CreateDashedLines('LineZ', {
      points: [new Vector3(0, 0, -camera.maxZ), new Vector3(0, 0, camera.maxZ)],
      dashSize: 2,
      gapSize: 1,
      dashNb: Math.floor(camera.maxZ * 5)
    });
    LineZ.color = Color3.Blue();
    LineZ.isVisible = false;

    let diffuseColor: Color3;
    scene.onPointerObservable.add((data, state) => {      
      
      if (data.type == PointerEventTypes.POINTERDOWN) {
        // 鼠标按下 改变选中颜色
        this.pickedMesh = data.pickInfo?.pickedMesh;
        if (this.pickedMesh) {
          diffuseColor = this.pickedMesh.material!.diffuseColor;
          this.pickedMesh.material!.diffuseColor = new Color3(0, 0.3, 0.8);
        }
      } else if (data.type == PointerEventTypes.POINTERUP && this.pickedMesh) {
        // 鼠标弹起 恢复模型原来的颜色
        const dist = Vector3.Distance(this.pickedMesh.position, Vector3.Zero());
        if (dist < 0.05 && dist > 0) {
          this.pickedMesh.position = Vector3.Zero();
          this.soundClosure.play();
        }
        this.pickedMesh.material!.diffuseColor = diffuseColor;
        this.pickedMesh.getBehaviorByName('PointerDrag')!.options = {};
        this.pickedMesh = null;
        // 隐藏参考线
        LineX.isVisible = LineY.isVisible = LineZ.isVisible = false;
      }      
    })

    scene.onKeyboardObservable.add(data => {
      let pdb: Nullable<PointerDragBehavior> = null;
      if (this.pickedMesh) {
        // 有模型部件被选中表示正在拖拽
        pdb = this.pickedMesh.getBehaviorByName('PointerDrag') as PointerDragBehavior;
        if (data.type == KeyboardEventTypes.KEYDOWN) {
          // 按下键盘时 (shift+x 设置pdb.options.dragPlaneNormal = Axis.X有问题)
          if (data.event.key.toLowerCase() == 'x') {
            pdb.options.dragAxis = Axis.X;
            LineX.position.y = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.y;
            LineX.position.z = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.z;
            LineX.isVisible = true;
          }
          if (data.event.key.toLowerCase() == 'y') {
            pdb.options.dragAxis = Axis.Y;
            LineY.position.x = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.x;
            LineY.position.z = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.z;
            LineY.isVisible = true;
          }
          if (data.event.key.toLowerCase() == 'z') {
            pdb.options.dragAxis = Axis.Z;
            LineZ.position.y = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.y;
            LineZ.position.x = this.pickedMesh.getRawBoundingInfo().boundingSphere.centerWorld.x;
            LineZ.isVisible = true;
          }
          
        } else if (data.type = KeyboardEventTypes.KEYUP) {
          pdb.options = {};
          LineX.isVisible = LineY.isVisible = LineZ.isVisible = false;
        }
        
      }
    })
    
  }

  private getMouseControl() {
    const dsm = new DeviceSourceManager(this.engine);

    let diffuseColor: Color3;
    dsm.getDeviceSource(DeviceType.Mouse)?.onInputChangedObservable.add((data, state) => {
      if (data.inputIndex == PointerInput.LeftClick) {
        const pickResult = this.scene.pick(this.scene.pointerX, this.scene.pointerY, (mesh) => {
          return mesh.isVisible && mesh.isReady;
        })
        if (pickResult.hit) {
          // 点击到物体
          this.pickedMesh = pickResult.pickedMesh;
          if (!this.pickedMesh) return;
          if (data.type == 'pointerdown') {
            diffuseColor = this.pickedMesh.material!.diffuseColor;
            this.pickedMesh.material!.diffuseColor = new Color3(0, 0.3, 0.8);
          }
        }
        if (data.type == 'pointerup' && this.pickedMesh) {
          this.pickedMesh.material!.diffuseColor = diffuseColor;
          this.pickedMesh = null;
        }
      }
      
    })
  }
}